package com.guassianblur;

import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;

public class GaussianBlur {

    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
    private Context context;
    private int width;
    private int height;
    private int frameBuffer;
    private int texture;
    private int shaderProgram;

    private int uOffsetLocation;
    private int uKernelLocation;
    private int uKernelSizeLocation;

    private EGLDisplay eglDisplay;
    private EGLContext eglContext;
    private EGLSurface eglSurface;

    public GaussianBlur(Context context, int width, int height) {
        this.context = context;
        this.width = width;
        this.height = height;
        initEGL();
        initOpenGL();
    }

    private void initEGL() {
        EGL10 egl = (EGL10) EGLContext.getEGL();
        eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        egl.eglInitialize(eglDisplay, new int[2]);

        int[] configurations = {
                EGL10.EGL_RENDERABLE_TYPE, 4,  // EGL_OPENGL_ES2_BIT
                EGL10.EGL_RED_SIZE, 8,
                EGL10.EGL_GREEN_SIZE, 8,
                EGL10.EGL_BLUE_SIZE, 8,
                EGL10.EGL_ALPHA_SIZE, 8,
                EGL10.EGL_DEPTH_SIZE, 16,
                EGL10.EGL_STENCIL_SIZE, 8,
                EGL10.EGL_NONE
        };

        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfigs = new int[1];
        egl.eglChooseConfig(eglDisplay, configurations, configs, 1, numConfigs);
        EGLConfig eglConfig = configs[0];

        int[] contextAttribs = {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL10.EGL_NONE
        };

        eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttribs);

        int[] surfaceAttribs = {
                EGL10.EGL_WIDTH, width,
                EGL10.EGL_HEIGHT, height,
                EGL10.EGL_NONE
        };

        eglSurface = egl.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs);
        egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
    }

    private void initOpenGL() {
        // Initialize OpenGL context, create framebuffer, texture, and shaders
        int[] frameBuffers = new int[1];
        GLES20.glGenFramebuffers(1, frameBuffers, 0);
        frameBuffer = frameBuffers[0];
        int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        texture = textures[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA,
                GLES20.GL_UNSIGNED_BYTE, null);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texture,
                0);

        shaderProgram = createShaderProgram();
    }

    private int createShaderProgram() {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                ShaderHelper.INSTANCE.getRawShaderResource(context, R.raw.vertex_shader));
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                ShaderHelper.INSTANCE.getRawShaderResource(context, R.raw.fragment_shader));

        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, vertexShader);
        GLES20.glAttachShader(program, fragmentShader);
        GLES20.glLinkProgram(program);

        // 获取uniform位置
        uOffsetLocation = GLES20.glGetUniformLocation(program, "u_Offset");
        uKernelLocation = GLES20.glGetUniformLocation(program, "u_Kernel");
        uKernelSizeLocation = GLES20.glGetUniformLocation(program, "u_KernelSize");

        return program;
    }

    private void setGaussianKernelAndOffset() {
        int kernelSize = 3; // 例如 3x3 核
        float[] kernel = {
                0.0625f, 0.125f, 0.0625f,
                0.125f, 0.25f, 0.125f,
                0.0625f, 0.125f, 0.0625f};
        float offset = 0.0015f; // 根据你的纹理尺寸调整

        GLES20.glUniform1f(uOffsetLocation, offset);
        GLES20.glUniform1fv(uKernelLocation, kernel.length, kernel, 0);
        GLES20.glUniform1i(uKernelSizeLocation, kernelSize);
    }

    private int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

    public Bitmap applyGaussianBlur(Bitmap bitmap) {
        // Load bitmap to texture
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        // Set the framebuffer
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer);
        GLES20.glViewport(0, 0, width, height);

        // Use the shader program
        GLES20.glUseProgram(shaderProgram);

        // 设置高斯核和偏移量
        setGaussianKernelAndOffset();

        // Bind the texture
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);

        // Render a quad that covers the entire screen
        renderQuad();

        // Read the pixels from the framebuffer
        ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);

        // Create a new bitmap from the buffer
        Bitmap blurredBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        buffer.rewind();
        blurredBitmap.copyPixelsFromBuffer(buffer);

        // 解绑
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        return blurredBitmap;
    }

    private void renderQuad() {
        // Define vertices and texture coordinates for a full-screen quad
        float[] vertices = {
                -1.0f, -1.0f, 0.0f, 0.0f,
                1.0f, -1.0f, 1.0f, 0.0f,
                -1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 1.0f, 1.0f};

        FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);

        int positionHandle = GLES20.glGetAttribLocation(shaderProgram, "a_Position");
        int texCoordHandle = GLES20.glGetAttribLocation(shaderProgram, "a_TexCoord");

        GLES20.glEnableVertexAttribArray(positionHandle);
        GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 4 * 4, vertexBuffer);

        vertexBuffer.position(2);
        GLES20.glEnableVertexAttribArray(texCoordHandle);
        GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 4 * 4, vertexBuffer);

        // Draw the quad
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        // Disable vertex arrays
        GLES20.glDisableVertexAttribArray(positionHandle);
        GLES20.glDisableVertexAttribArray(texCoordHandle);
    }

    // Cleanup method to free resources
    public void cleanup() {
        GLES20.glDeleteFramebuffers(1, new int[]{frameBuffer}, 0);
        GLES20.glDeleteTextures(1, new int[]{texture}, 0);
        GLES20.glDeleteProgram(shaderProgram);

        EGL10 egl = (EGL10) EGLContext.getEGL();
        egl.eglDestroySurface(eglDisplay, eglSurface);
        egl.eglDestroyContext(eglDisplay, eglContext);
        egl.eglTerminate(eglDisplay);
    }
}